package com.cyys.modules.config.aop;

import com.cyys.common.annotation.SystemLog;
import com.cyys.modules.core.shiro.token.manage.TokenManager;
import com.cyys.modules.system.model.log.Log;
import com.cyys.modules.system.model.user.User;
import com.cyys.modules.system.service.log.LogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
public class LogAspect {

	private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
	// 注入service,用来将日志信息保存在数据库
	@Resource
	private LogService logService;

	@Resource
	private ThreadPoolTaskExecutor threadPoolTaskExecutor;

	@Autowired(required = false)
	private HttpServletRequest request;

	private static final ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime");
	private static final ThreadLocal<Log> logThreadLocal = new NamedThreadLocal<>("ThreadLocal log");

	// 配置接入点
	@Pointcut("@annotation(com.cyys.common.annotation.SystemLog)")
	private void controllerAspect() {
	}// 定义一个切入点

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作的开始时间
	 *
	 * @param joinPoint
	 *            切点
	 * @throws InterruptedException
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) throws InterruptedException {
		Date beginTime = new Date();
		beginTimeThreadLocal.set(beginTime);// 线程绑定变量（该数据只有当前请求的线程可见）
		if (logger.isDebugEnabled()) {// 这里日志级别为debug
			logger.debug("开始计时: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(beginTime) + "  URI: {}"
					+ ": " + request.getRequestURI());
		}
	}

	/**
	 * 后置通知 用于拦截Controller层记录用户的操作
	 *
	 * @param joinPoint
	 *            切点
	 */
	@After("controllerAspect()")
	public void doAfter(JoinPoint joinPoint) {
		String userId = null;
		String desp = "info"; // 日志类型(info:入库,error:错误)
		String remoteAddr = request.getRemoteAddr();// 请求的IP
		String requestUri = request.getRequestURI();// 请求的Uri
		String requestMethod = request.getMethod(); // 请求的方法类型(post/get)
		Map<String, String[]> params = request.getParameterMap(); // 请求提交的参数
		Map<String, String> logMap = getControllerMethodDescription2(joinPoint);
		User user = TokenManager.getToken();
		if (user != null) {
			userId = user.getId();
		}else if(logMap.get("module").equals("用户登录")){
			logMap.put("methods","登录失败");
		}
		Log log = new Log();
		if (userId != null) {
			log.setUserId(userId);
			log.setTerminal("PC");
		}else{
			log.setTerminal("MOBILE");
		}
		// 打印JVM信息。
		long beginTime = beginTimeThreadLocal.get().getTime();// 得到线程绑定的局部变量（开始时间）
		long endTime = System.currentTimeMillis(); // 2、结束时间
		if (logger.isDebugEnabled()) {
			logger.debug("计时结束：{" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime) + "}  URI: {"
					+ request.getRequestURI() + "}  耗时： {" + (endTime - beginTime) + "}   最大内存: {"
					+ Runtime.getRuntime().maxMemory() / 1024 / 1024 + "}m  已分配内存: {"
					+ Runtime.getRuntime().totalMemory() / 1024 / 1024 + "}m  已分配内存中的剩余空间: {"
					+ Runtime.getRuntime().freeMemory() / 1024 / 1024 + "}m  最大可用内存: {"
					+ (Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()
							+ Runtime.getRuntime().freeMemory()) / 1024 / 1024
					+ "}m");
		}
		if(logMap.get("module").equals("用户登录")){
			log.setType("1");
		}else
			log.setType("2");
		log.setMethod(logMap.get("methods"));
		log.setModule(logMap.get("module"));
		log.setResponseData("" + (endTime - beginTime));
		log.setIp(remoteAddr);
		log.setRequestUri(requestUri);
		log.setRequestMethod(requestMethod);
		log.setMapToParams(params);
		log.setData(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(beginTimeThreadLocal.get()));
		log.setDesp(desp);
		// 1.直接执行保存操作
		// this.logService.createSystemLog(log);
		// 2.优化:异步保存日志
		// new SaveLogThread(log, logService).start();
		// 3.再优化:通过线程池来执行日志保存
		threadPoolTaskExecutor.execute(new SaveLogThread(log, logService));
		logThreadLocal.set(log);

	}

	/**
	 * 异常通知 记录操作报错日志
	 *
	 * @param joinPoint
	 * @param e
	 */
	@AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		Log log = logThreadLocal.get();
		if(log == null)
			log = new Log();
		// log.setType("error");
		log.setDesp("error");
		log.setException(e.toString());
		new UpdateLogThread(log, logService).start();
	}

	/**
	 * 获取注解中对方法的描述信息 用于service层注解
	 *
	 * @param joinPoint 切点
	 * @return discription
	 */
	public static Map<String, String> getServiceMthodDescription2(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SystemLog systemlog = method.getAnnotation(SystemLog.class);
		Map<String, String> logMap = new HashMap<String, String>();
		logMap.put("methods", systemlog.methods());
		logMap.put("module", systemlog.module());
		return logMap;
	}

	/**
	 * 获取注解中对方法的描述信息 用于Controller层注解
	 *
	 * @param joinPoint
	 *            切点
	 * @return discription
	 */
	public static Map<String, String> getControllerMethodDescription2(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		SystemLog systemlog = method.getAnnotation(SystemLog.class);
		Map<String, String> logMap = new HashMap<String, String>();
		logMap.put("methods", systemlog.methods());
		logMap.put("module", systemlog.module());
		return logMap;
	}

	/**
	 * 保存日志线程
	 */
	private static class SaveLogThread implements Runnable {
		private Log log;
		private LogService logService;

		public SaveLogThread(Log log, LogService logService) {
			this.log = log;
			this.logService = logService;
		}

		@Override
		public void run() {
			try {
				logService.save(log);
			} catch (Exception e) {
				logger.error(e.getMessage());
			}
		}
	}

	/**
	 * 日志更新线程
	 */
	private static class UpdateLogThread extends Thread {
		private Log log;
		private LogService logService;

		public UpdateLogThread(Log log, LogService logService) {
			super(UpdateLogThread.class.getSimpleName());
			this.log = log;
			this.logService = logService;
		}

		@Override
		public void run() {
			try {
				this.logService.update(log);
			} catch (Exception e) {
				logger.error(e.getMessage());
			}
		}
	}
}
